package com.example.ephuizi.stl.util;


import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.util.concurrent.CountDownLatch;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import android.util.Log;

/**
 * 注意java byte范围-128~127 Created by ephuizi@gmail.com on 2015/7/25.
 */
public class STLUtil {

    private static String TAG = STLUtil.class.getSimpleName();

    // final private static Pattern ASCII_PATTERN_FACET =
    // Pattern.compile("facet([\\s\\S]*?)endfacet");
    final private static Pattern ASCII_PATTERN_NORMAL = Pattern
            .compile("normal[\\s]+([\\-+]?[0-9]+\\.?[0-9]*([eE][\\-+]?[0-9]+)?)+[\\s]+([\\-+]?[0-9]*\\.?[0-9]+([eE][\\-+]?[0-9]+)?)+[\\s]+([\\-+]?[0-9]*\\.?[0-9]+([eE][\\-+]?[0-9]+)?)+");
    final private static Pattern ASCII_PATTERN_VERTEX = Pattern
            .compile("vertex[\\s]+([\\-+]?[0-9]+\\.?[0-9]*([eE][\\-+]?[0-9]+)?)+[\\s]+([\\-+]?[0-9]*\\.?[0-9]+([eE][\\-+]?[0-9]+)?)+[\\s]+([\\-+]?[0-9]*\\.?[0-9]+([eE][\\-+]?[0-9]+)?)+");

    /**
     * 判断是否stl格式
     *
     * @param stlPath
     * @return true binary false ascii
     */
    public static boolean isBinary(String stlPath) {
        long t_begin = System.currentTimeMillis();

        long expect = 0;// 以binnary方式计算的文件大小;
        int face_size = (32 / 8 * 3) + ((32 / 8 * 3) * 3) + (16 / 8);// 一个三角片大小
        int n_facetNum = 0;// 三角片数量
        RandomAccessFile stl = null;
        try {
            stl = new RandomAccessFile(stlPath, "r");

            stl.seek(80);
            byte[] arr = {0, 0, 0, 0};
            stl.read(arr);
            n_facetNum = STLFaceNum(arr);

            expect = 80 + (32 / 8) + (n_facetNum * face_size);
            if (expect == stl.length()) {
                stl.close();
                logUseTime(TAG, "isBinary", t_begin);
                return true;
            }
            stl.close();

            // some binary files will have different size from expected,
            // checking characters lower than ASCII to confirm is binary
            try {
                int n;
                // 直接缓冲
                FileInputStream fis = new FileInputStream(stlPath);
                byte buf[] = new byte[2048];
                while ((n = fis.read(buf)) != -1) { // FileInputStream的read方法直接读入字节块到
                    // 直接缓冲buf，然后每次读取一个字节
                    for (int i = 0; i < n; i++) {
                        if (buf[i] < 0) {
                            logUseTime(TAG, "isBinary", t_begin);
                            fis.close();
                            return true;
                        }

                    }
                }
                fis.close();

            } catch (FileNotFoundException e) {
                Log.e(TAG, e.getMessage());
            } catch (IOException e) {
                Log.e(TAG, e.getMessage());
            }

            logUseTime(TAG, "isBinary", t_begin);
            return false;

        } catch (FileNotFoundException e) {
            Log.e(TAG, e.getMessage());
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
        }

        return false;
    }

    /**
     * 用于正数,因为负数存储是补码 第81~84四个字节
     *
     * @param arr
     * @return
     */
    public static int STLFaceNum(byte[] arr) {
        if (arr != null && arr.length == 4) {
            int a = arr[0] & (0xFF);// 防止低位转二进制后是变成负数
            int b = (arr[1] << 8) & (0xFFFF);
            int c = (arr[2] << 16) & (0xFFFFFF);
            int d = (arr[3] << 24) & (0xFFFFFFFF);
            return a + b + c + d;
        }
        return -1;
    }

    /**
     * resolve binary stl file
     *
     * @param stlPath
     * @return
     */
    public static STLFile parseBinary(String stlPath, STLToArrProgress progress) {
        // 84+50 * fa
        // 多线程加载
        long t_begin = System.currentTimeMillis();
        RandomAccessFile stl;
        int facetNum = 0;
        try {
            stl = new RandomAccessFile(stlPath, "r");
            stl.seek(80);
            byte[] arr = {0, 0, 0, 0};
            stl.read(arr);
            facetNum = STLFaceNum(arr);// 总面数
            progress.setTotalFacet(facetNum);
            stl.close();
        } catch (FileNotFoundException e) {
            Log.v(TAG, "FileNotFoundException " + e.getMessage());
        } catch (IOException e) {
            Log.v(TAG, "IOException " + e.getMessage());
        }

        final int THREAD_NUM = calculateThreadNum(stlPath);
        final CountDownLatch latch = new CountDownLatch(THREAD_NUM);
        final float[] vertices = new float[facetNum * 3 * 3];
        final float[] normals = new float[facetNum * 3 * 3];// 三角面片法向量的3个分量值数据

        if (facetNum > 0) {
            int gap = facetNum / THREAD_NUM;
            for (int i = 0; i < THREAD_NUM; i++) {
                final int startFacet = i * gap + 1;
                int endF = startFacet + gap;
                if (THREAD_NUM == i + 1) {
                    endF = facetNum + 1;
                }
                final int endFacet = endF;
                exploreBinarySTLThread(latch, stlPath, startFacet, endFacet, vertices, normals, progress);
            }
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            Log.v(TAG, "latch " + e.getMessage());
        }
        float[] max = new float[3];
        float[] min = new float[3];
        setMaxAndMin(max, min, vertices);
        progress.setNowFacet(facetNum);
        logUseTime(TAG, "parseBinary", t_begin);
        Log.e(TAG, "parseBinary finish");
        return new STLFile(facetNum, max, min, vertices, normals);

    }

    private static int calculateThreadNum(String stlPath) {
        File f = new File(stlPath);
        long size = f.length();
        int min = 2;
        int max = 10;
        int M_500KB = 500 * 1024;
        int num = Long.valueOf(size / M_500KB).intValue();
        if (num < min)
            return min;
        if (num > max)
            return max;
        return num;
    }

    private static void exploreBinarySTLThread(final CountDownLatch latch, final String stlPath, final int startFacet, final int endFacet,
                                               final float[] vertices, final float[] normals, final STLToArrProgress progress) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    byte tri[] = new byte[50];
                    int offset = 3 * 3 * (startFacet - 1);
                    RandomAccessFile stl = new RandomAccessFile(stlPath, "r");
                    stl.seek(84 + (startFacet - 1) * 50);
                    for (int face = startFacet; face < endFacet; face++) {
                        // progress
                        progress.setNowFacet(progress.getNowFacet() + 1);

                        // 法向量12个字节
                        stl.read(tri);


                        float normalX = STLUtil.toFloat(tri, 0);// 4

                        float normalY = STLUtil.toFloat(tri, 4);// 4

                        float normalZ = STLUtil.toFloat(tri, 8);// 4

                        // 顶点坐标36字节
                        for (int i = 1; i <= 3; i++) {

                            vertices[offset] = STLUtil.toFloat(tri, 12 * i);// x

                            vertices[offset + 1] = STLUtil.toFloat(tri, 12 * i + 4);// y

                            vertices[offset + 2] = STLUtil.toFloat(tri, 12 * i + 8);// z

                            normals[offset] = normalX;
                            normals[offset + 1] = normalY;
                            normals[offset + 2] = normalZ;

                            offset += 3;// 增加位移
                        }
                        // color2字节
                        // instead of above to skip color
                    }
                    stl.close();
                    Log.v(TAG, "startFacet : " + startFacet + " endFacet : " + endFacet);

                } catch (FileNotFoundException e) {
                    Log.v(TAG, "FileNotFoundException " + e.getMessage());
                } catch (IOException e) {
                    Log.v(TAG, "IOException " + e.getMessage());
                } finally {
                    latch.countDown();
                }

            }
        });
        t.start();

    }

    public static STLFile parseASCII(String stlPath, STLToArrProgress progress) {
        int facetNum = asciiFacetNum(stlPath);
        progress.setTotalFacet(facetNum);// 转化进度

        RandomAccessFile stl = null;
        try {
            stl = new RandomAccessFile(stlPath, "r");
            float[] vertices = new float[facetNum * 3 * 3];
            float[] normals = new float[facetNum * 3 * 3];// 三角面片法向量的3个分量值数据
            float[] max = new float[3];
            float[] min = new float[3];

            final String FACET_END = "endfacet";
            StringBuffer bf = new StringBuffer();// record one-facet

            int facetIndex = 0;
            String line = null;
            while ((line = stl.readLine()) != null) {
                bf.append(line);
                if (line.length() > 8 && line.length() < 15 && line.contains(FACET_END)) {
                    // one facet
                    String oneFacet = bf.toString();
                    Matcher nMatcher = ASCII_PATTERN_NORMAL.matcher(oneFacet);
                    if (!nMatcher.find())
                        continue;
                    String normal = nMatcher.group();

                    Matcher mV = ASCII_PATTERN_VERTEX.matcher(oneFacet);
                    if (!mV.find())
                        continue;
                    String v1 = mV.group();// 第一个顶点
                    if (!mV.find())
                        continue;
                    String v2 = mV.group();// 第二个顶点
                    if (!mV.find())
                        continue;
                    String v3 = mV.group();// 第三个顶点

                    // 解析法向量
                    String GAP = " ";

                    int nfIndex = facetIndex * 9;
                    String[] n_f_arr = normal.split(GAP);
                    normals[nfIndex + 6] = normals[nfIndex + 3] = normals[nfIndex] = Float.parseFloat(n_f_arr[1]);
                    normals[nfIndex + 1 + 6] = normals[nfIndex + 1 + 3] = normals[nfIndex + 1] = Float.parseFloat(n_f_arr[2]);
                    normals[nfIndex + 2 + 6] = normals[nfIndex + 2 + 3] = normals[nfIndex + 2] = Float.parseFloat(n_f_arr[3]);
                    // 解析顶点
                    String[] v1_f_arr = v1.split(GAP);
                    vertices[nfIndex + 0] = Float.parseFloat(v1_f_arr[1]);// x
                    vertices[nfIndex + 1] = Float.parseFloat(v1_f_arr[2]);// y
                    vertices[nfIndex + 2] = Float.parseFloat(v1_f_arr[3]);// z

                    String[] v2_f_arr = v2.split(GAP);
                    vertices[nfIndex + 3] = Float.parseFloat(v2_f_arr[1]);
                    vertices[nfIndex + 4] = Float.parseFloat(v2_f_arr[2]);
                    vertices[nfIndex + 5] = Float.parseFloat(v2_f_arr[3]);

                    String[] v3_f_arr = v3.split(GAP);
                    vertices[nfIndex + 6] = Float.parseFloat(v3_f_arr[1]);
                    vertices[nfIndex + 7] = Float.parseFloat(v3_f_arr[2]);
                    vertices[nfIndex + 8] = Float.parseFloat(v3_f_arr[3]);

                    // update max and min
                    if (nfIndex == 0) {
                        update(max, min, vertices[nfIndex + 0], vertices[nfIndex + 1], vertices[nfIndex + 1], true);
                    } else {
                        update(max, min, vertices[nfIndex + 0], vertices[nfIndex + 1], vertices[nfIndex + 1], false);
                    }
                    update(max, min, vertices[nfIndex + 3], vertices[nfIndex + 4], vertices[nfIndex + 5], false);
                    update(max, min, vertices[nfIndex + 6], vertices[nfIndex + 7], vertices[nfIndex + 8], false);

                    // set bf count=0
                    facetIndex++;
                    bf.setLength(0);

                    // 转化进度
                    progress.setNowFacet(facetIndex);
                }
            }

            stl.close();
            // 转化进度
            progress.setNowFacet(facetNum);
            return new STLFile(facetNum, max, min, vertices, normals);

        } catch (FileNotFoundException e) {
            Log.e(TAG, e.getMessage());
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
        }
        return null;
    }

    /**
     * 多线程,粗狂式解析
     *
     * @param stlPath
     * @param progress
     * @return
     */
    public static STLFile parseASCII2(String stlPath, STLToArrProgress progress) {
        long t_begin = System.currentTimeMillis();
        logUseTime(TAG, "isBinary", t_begin);
        int facetNum = asciiFacetNum(stlPath);
        progress.setTotalFacet(facetNum);

        int threadNum = calculateThreadNum(stlPath);
        long beginPos[] = calculateThreadBeginPos(stlPath, facetNum, threadNum);
        float[] vertices = new float[facetNum * 3 * 3];
        float[] normals = new float[facetNum * 3 * 3];// 三角面片法向量的3个分量值数据
        CountDownLatch latch = new CountDownLatch(threadNum);
        float[] max = new float[3];
        float[] min = new float[3];

        int size = facetNum / threadNum;// 每个线程需要解析的面数
        for (int i = 0; i < beginPos.length; i++) {
            exploreAsciiSTLThread(i, size, facetNum, latch, stlPath, beginPos, vertices, normals, max, min, progress);
        }
        try {
            latch.await();
            progress.setNowFacet(facetNum);
            logUseTime(TAG, "parseASCII2", t_begin);
            Log.e(TAG, "parseASCII2 finish");
            return new STLFile(facetNum, max, min, vertices, normals);
        } catch (InterruptedException e) {
            Log.e(TAG, e.getMessage());
        }
        Log.e(TAG, "parseASCII2 error");
        return null;
    }

    /**
     * 计算ascii-stl-file三角片数量
     *
     * @param stlPath
     * @return
     */
    public static final int asciiFacetNum(String stlPath) {
        int facetNum = 0;
        try {
            int cnt = 0;
            int n;
            // 直接缓冲
            FileInputStream fis = new FileInputStream(stlPath);
            byte buf[] = new byte[2048];
            while ((n = fis.read(buf)) != -1) { // FileInputStream的read方法直接读入字节块到
                // 直接缓冲buf，然后每次读取一个字节
                for (int i = 0; i < n; i++) {
                    if (buf[i] == '\n')
                        cnt++;
                }
            }

            facetNum = cnt / 7;
            fis.close();

        } catch (FileNotFoundException e) {
            Log.e(TAG, e.getMessage());
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
        }
        return facetNum;
    }

    private static long[] calculateThreadBeginPos(String stlPath, int facetNum, int threadNum) {
        long beginPos[] = new long[threadNum];
        int size = facetNum / threadNum;// 每个线程需要解析的面数

        try {
            long hadReadCnt = 0;
            int n = 0;
            int r_line = 0;// 实际行数
            int f_cnt = 0;// 计算第几个三角片数量

            FileInputStream fis = new FileInputStream(stlPath);
            byte buf[] = new byte[2048];
            while ((n = fis.read(buf)) != -1) { // FileInputStream的read方法直接读入字节块到
                // 直接缓冲buf，然后每次读取一个字节
                for (int i = 0; i < n; i++) {
                    if (buf[i] == '\n') {
                        r_line++;
                        if ((r_line - 1) % 7 == 0) {
                            f_cnt++;
                            // f_cnt - 1=0,第一个三角片
                            if ((f_cnt - 1) % size == 0) {
                                int index = f_cnt / size;
                                if (index < beginPos.length) {
                                    beginPos[index] = hadReadCnt + i;
                                }
                            }

                        }
                    }
                }
                hadReadCnt += n;
            }
            fis.close();

        } catch (FileNotFoundException e) {
            Log.e(TAG, e.getMessage());
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
        }
        return beginPos;
    }

    private static void exploreAsciiSTLThread(int index, int size, int facetNum, final CountDownLatch latch, final String stlPath,
                                              long beginPos[], final float[] vertices, final float[] normals, final float[] max, final float[] min,
                                              final STLToArrProgress progress) {
        String GAP = " ";
        FileInputStream fin;
        try {
            fin = new FileInputStream(stlPath);

            BufferedReader bufReader = new BufferedReader(new InputStreamReader(fin));
            bufReader.skip(beginPos[index] + 1);

            int facetCnt = 0;// 本批次第几个三角片
            int r_size;
            // 最后一个线程计算的面可能要多一些
            if (index + 1 == beginPos.length) {
                r_size = facetNum - size * beginPos.length + size;
            } else {
                r_size = size;
            }
            Log.e(TAG, "thread index " + index + " start " + "r_size " + r_size);
            String l_normal = "0.00 0.00 0.01";// 防止法向量丢失
            while (facetCnt < r_size) {
                facetCnt++;
                boolean isNLost = false;// 法向量丢失
                int begin = bufReader.read();
                while (begin != '-' && (begin < '0' || begin > '9')) {
                    begin = bufReader.read();
                    if (begin == 'N') {
                        // 法向量丢失,NaN NaN NaN情况
                        isNLost = true;
                        break;
                    }
                }
                if (!isNLost)
                    l_normal = (char) begin + bufReader.readLine();
                begin = bufReader.read();
                while (begin != '-' && (begin < '0' || begin > '9')) {
                    begin = bufReader.read();
                }
                String l_vertice1 = (char) begin + bufReader.readLine();
                begin = bufReader.read();
                while (begin != '-' && (begin < '0' || begin > '9')) {
                    begin = bufReader.read();
                }
                String l_vertice2 = (char) begin + bufReader.readLine();
                begin = bufReader.read();
                while (begin != '-' && (begin < '0' || begin > '9')) {
                    begin = bufReader.read();
                }
                String l_vertice3 = (char) begin + bufReader.readLine();

                // 解析法向量
                int r_f_cnt = index * size + facetCnt;
                int nfIndex = (r_f_cnt - 1) * 9;
                String[] n_f_arr = l_normal.split(GAP);
                normals[nfIndex + 6] = normals[nfIndex + 3] = normals[nfIndex] = Float.parseFloat(n_f_arr[0]);
                normals[nfIndex + 1 + 6] = normals[nfIndex + 1 + 3] = normals[nfIndex + 1] = Float.parseFloat(n_f_arr[1]);
                normals[nfIndex + 2 + 6] = normals[nfIndex + 2 + 3] = normals[nfIndex + 2] = Float.parseFloat(n_f_arr[2]);
                // 解析顶点
                String[] v1_f_arr = l_vertice1.split(GAP);
                vertices[nfIndex + 0] = Float.parseFloat(v1_f_arr[0]);// x
                vertices[nfIndex + 1] = Float.parseFloat(v1_f_arr[1]);// y
                vertices[nfIndex + 2] = Float.parseFloat(v1_f_arr[2]);// z
                update(max, min, vertices[nfIndex + 0], vertices[nfIndex + 1], vertices[nfIndex + 2], false);

                String[] v2_f_arr = l_vertice2.split(GAP);
                vertices[nfIndex + 3] = Float.parseFloat(v2_f_arr[0]);
                vertices[nfIndex + 4] = Float.parseFloat(v2_f_arr[1]);
                vertices[nfIndex + 5] = Float.parseFloat(v2_f_arr[2]);
                update(max, min, vertices[nfIndex + 3], vertices[nfIndex + 4], vertices[nfIndex + 5], false);

                String[] v3_f_arr = l_vertice3.split(GAP);
                vertices[nfIndex + 6] = Float.parseFloat(v3_f_arr[0]);
                vertices[nfIndex + 7] = Float.parseFloat(v3_f_arr[1]);
                vertices[nfIndex + 8] = Float.parseFloat(v3_f_arr[2]);
                update(max, min, vertices[nfIndex + 6], vertices[nfIndex + 7], vertices[nfIndex + 8], false);

                progress.setNowFacet(progress.getNowFacet() + 1);
            }
            bufReader.close();
            fin.close();

            Log.e(TAG, "thread index " + index + " finish");
        } catch (FileNotFoundException e) {
            Log.e(TAG, e.getMessage());
        } catch (IOException e) {
            Log.e(TAG, e.getMessage());
        } finally {
            latch.countDown();
        }

    }

    /**
     * 更新最高点与最低点
     *
     * @param max
     * @param min
     * @param vertices
     */
    private static void setMaxAndMin(float[] max, float[] min, float[] vertices) {
        if (vertices.length % 3 == 0) {
            max[0] = min[0] = vertices[0];
            max[1] = min[1] = vertices[1];
            max[2] = min[2] = vertices[2];

            for (int i = 0; i + 2 < vertices.length; i += 3) {
                updateMax(max, vertices[i], vertices[i + 1], vertices[i + 2]);
                updateMin(min, vertices[i], vertices[i + 1], vertices[i + 2]);
            }
        }
    }

    /**
     * 更新最高点与最低点
     *
     * @param max
     * @param min
     * @param x
     * @param y
     * @param z
     * @param isInit
     */
    public static void update(float[] max, float[] min, float x, float y, float z, boolean isInit) {
        if (isInit) {
            max[0] = x;
            max[1] = y;
            max[2] = z;
            min[0] = x;
            min[1] = y;
            min[2] = z;
        } else {
            updateMax(max, x, y, z);
            updateMin(min, x, y, z);
        }
    }

    private static void updateMax(float[] max, float x, float y, float z) {
        if (max[0] < x) {
            max[0] = x;
        }
        if (max[1] < y) {
            max[1] = y;
        }
        if (max[2] < z) {
            max[2] = z;
        }
    }

    private static void updateMin(float[] min, float x, float y, float z) {
        if (min[0] > x) {
            min[0] = x;
        }
        if (min[1] > y) {
            min[1] = y;
        }
        if (min[2] > z) {
            min[2] = z;
        }
    }

    /**
     * -1 原码1000,0001 反码1111,1110 补码1111,1111 所以无符号值 255
     *
     * @param b
     * @return
     */
    public static int toInt(byte b) {
        return (int) (b & 0xFF);
    }

    /**
     * -1 原码1000,0001 反码1111,1110 补码1111,1111 所以无符号值 255 带符号位
     *
     * @param b
     * @return
     */
    public static float toFloat(byte b) {
        return (float) (b & 0xFF);
    }

    /**
     * 字节转换为浮点
     *
     * @param lowFirstBytes
     * @return
     */
    public static float toFloat(byte[] lowFirstBytes) {
        return toFloat(lowFirstBytes, 0);
    }

    public static float toFloat(byte[] lowFirstBytes, int start) {
        int l = (lowFirstBytes[start] & 0xFF) | (lowFirstBytes[start + 1] << 8 & 0xFFFF) | (lowFirstBytes[start + 2] << 16 & 0xFFFFFF)
                | (lowFirstBytes[start + 3] << 24 & 0xFFFFFFFF);
        return Float.intBitsToFloat(l);
    }

    public static STLFile parse(String path) {
        return parse(path, new STLToArrProgress());
    }

    public static STLFile parse(String path, STLToArrProgress progress) {
        if (STLUtil.isBinary(path)) {
            return STLUtil.parseBinary(path, progress);
        } else {
            Log.e(TAG, "STLUtil.parseASCII2 start");
            return STLUtil.parseASCII2(path, progress);
        }
    }

    public static void logUseTime(String tag, String mothedName, long begin) {
        long u = System.currentTimeMillis() - begin;
        Log.v(tag, mothedName + " use time :" + Long.toString(u / 1000) + " s");
    }

}